// -----------------------------------------------------------------------
//  <copyright file="SettingStore.cs" company="LiuliuSoft">
//      Copyright (c) 2022-2024 DaprPlus. All rights reserved.
//  </copyright>
//  <site>https://www.dapr.plus</site>
//  <last-editor>郭明锋</last-editor>
//  <last-date>2024-04-15 21:24</last-date>
// -----------------------------------------------------------------------

using DaprPlus.Domain;
using DaprPlus.Domain.Entity;
using DaprPlus.Properties;

using Microsoft.Extensions.Caching.Distributed;

using OSharp.Caching;


namespace DaprPlus.Settings;

public class SettingStore(IServiceProvider provider) : ISettingStore
{
    private readonly IRepository<SettingItem> _repository = provider.GetRequiredService<IRepository<SettingItem>>();
    private readonly IDistributedCache _cache = provider.GetRequiredService<IDistributedCache>();
    private static readonly Random _random = new();

    /// <summary>
    /// 获取指定键名的设置项
    /// </summary>
    /// <param name="key">键名</param>
    /// <param name="token"></param>
    /// <returns></returns>
    public async Task<ISettingItem?> GetByKeyAsync(string key, CancellationToken token = default)
    {
        Check.NotNullOrEmpty(key, nameof(key));

        // 有效期为 7 ± 1 天，防止缓存集中过期
        var seconds = 7 * 24 * 3600 + _random.Next(-24 * 3600, 24 * 3600);
        var cacheKey = GetCacheKey(key);
        var value = await _cache.GetAsync(cacheKey, async token1 =>
        {
            var spec = new SettingItemByKeySpec(key);
            var item = await _repository.GetAsync(spec, token1);
            return item;
        }, seconds, token);
        return value;
    }

    /// <summary>
    /// 获取指定根节点的设置项，如输入“ServiceName:Users.”，返回所有以此开头的设置项
    /// </summary>
    /// <param name="rootKey">键根节点</param>
    /// <param name="token"></param>
    /// <returns></returns>
    public async Task<ISettingItem[]> GetByRootKeyAsync(string rootKey, CancellationToken token = default)
    {
        var keys = await GetKeysAsync(rootKey, token);
        var items = new List<ISettingItem>();
        foreach (var key in keys)
        {
            var item = await GetByKeyAsync(key, token);
            if (item == null)
            {
                continue;
            }
            items.Add(item);
        }
        return items.ToArray();
    }

    /// <summary>
    /// 创建或更新键值对信息
    /// </summary>
    /// <param name="key">键</param>
    /// <param name="value">值</param>
    /// <param name="token"></param>
    /// <returns></returns>
    public Task<ApiResult> CreateOrUpdateAsync(string key, object value, CancellationToken token = default)
    {
        Check.NotNullOrEmpty(key, nameof(key));
        Check.NotNull(value, nameof(value));

        var pair = new SettingItem() { Key = key, Value = value };
        return CreateOrUpdateAsync(new ISettingItem[] { pair }, token);
    }

    public async Task<ApiResult> CreateOrUpdateAsync(ISettingItem[] dtos, CancellationToken token = default)
    {
        Check.NotNull(dtos, nameof(dtos));
        if (dtos.Length == 0)
        {
            return new ApiResult(ApiResultType.Info);
        }

        var items = dtos.Cast<SettingItem>().ToArray();
        var refreshKeys = new List<string>();
        var uow = provider.GetUnitOfWork(true);
        foreach (var dto in items)
        {
            var spec = new SettingItemByKeySpec(dto.Key);
            var item = await _repository.GetAsync(spec, token);
            if (item == null)
            {
                item = dto;
                item.LastUpdatedTime = DateTime.Now;
                await _repository.InsertAsync(item, token);
            }
            else
            {
                if (item.Value != dto.Value)
                {
                    item.Value = dto.Value;
                    item.LastUpdatedTime = DateTime.Now;
                }
                item.Display = dto.Display;
                item.Remark = dto.Remark;
                item.Order = dto.Order;
                item.IsLocked = dto.IsLocked;
                var count = await _repository.UpdateAsync(new[]{ item }, token);
                refreshKeys.AddIf(item.Key, count > 0);
            }
        }

        await uow.CommitAsync(token);

        var cacheKeys = refreshKeys.Select(GetCacheKey).ToArray();
        await _cache.RemoveAsync(token, cacheKeys);
        return new ApiResult(ApiResultType.Success);
    }

    public async Task<ApiResult> DeleteAsync(string[] keys, CancellationToken token = default)
    {
        var uow = provider.GetUnitOfWork(true);
        var spec = new SettingItemByKeysSpec(keys);
        await _repository.DeleteBatchAsync(spec, token);
        await uow.CommitAsync(token);

        var cacheKeys = keys.Select(GetCacheKey).ToArray();
        await _cache.RemoveAsync(token, cacheKeys);
        return new ApiResult(ApiResultType.Success, ResDaprPlus.Format_SettingStore_ItemDelete_Success.FormatWith(keys.Length));
    }

    public async Task<ApiResult> DeleteByRootKeyAsync(string rootKey, CancellationToken token = default)
    {
        var keys = await GetKeysAsync(rootKey, token);
        return await DeleteAsync(keys, token);
    }

    /// <summary>
    /// 构建指定类型的设置信息
    /// </summary>
    /// <typeparam name="TSetting"></typeparam>
    /// <returns></returns>
    public async Task<TSetting> BuildSettingAsync<TSetting>(CancellationToken token = default) where TSetting : ISetting, new()
    {
        var setting = new TSetting();
        var type = typeof(TSetting);
        var settingObj = await BuildSettingAsync(type, setting, token);
        return (TSetting)settingObj;
    }

    /// <summary>
    /// 构建指定类型的设置信息
    /// </summary>
    /// <param name="settingType">设置类型</param>
    /// <param name="setting">设置类型对象</param>
    /// <param name="token"></param>
    /// <returns></returns>
    public async Task<ISetting> BuildSettingAsync(Type settingType, ISetting? setting = default, CancellationToken token = default)
    {
        if (!settingType.IsBaseOn<ISetting>())
        {
            throw new DaprPlusException(
                ResDaprPlus.Format_SettingStore_TypeNotImplInterface.FormatWith(settingType, typeof(ISetting)));
        }

        if (setting == null)
        {
            var obj = Activator.CreateInstance(settingType);
            setting = (ISetting)obj! ?? throw new DaprPlusException(
                ResDaprPlus.Format_SettingStore_SettingType_InstantFail.FormatWith(settingType));
        }

        var properties = settingType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(p => p.PropertyType.IsBaseOn<ISettingItem>()).ToArray();
        foreach (var property in properties)
        {
            var value = property.GetValue(setting);
            if (value is not ISettingItem item)
            {
                continue;
            }
            var key = item.Key;
            var dbItem = await GetByKeyAsync(key, token);
            if (dbItem == null)
            {
                continue;
            }

            var propType = property.PropertyType;
            if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(SettingItem<>))
            {
                var genericType = propType.GetGenericArguments()[0];
                var convertedValue = ConvertToGenericSettingItem(genericType, dbItem);
                property.SetValue(setting, convertedValue);
            }
            else
            {
                property.SetValue(setting, dbItem);
            }
        }

        return setting;
    }

    private static object ConvertToGenericSettingItem(Type genericType, ISettingItem item)
    {
        var genericSettingItemType = typeof(SettingItem<>).MakeGenericType(genericType);
        var constructor = genericSettingItemType.GetConstructor(new[] { typeof(string), genericType });
        if (constructor == null)
        {
            throw new InvalidOperationException(ResDaprPlus.String_SettingStore_SettingItemT_Ctor);
        }

        // 调用 GetValue<T> 来安全地获取值
        var valueMethod = typeof(SettingItem).GetMethod("GetValue")!.MakeGenericMethod(genericType);
        var value = valueMethod.Invoke(item, null);
        return constructor.Invoke(new[] { item.Key, value });
    }

    /// <summary>
    /// 保存指定类型的设置信息
    /// </summary>
    /// <typeparam name="TSetting"></typeparam>
    /// <param name="setting"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    public async Task<ApiResult> SaveSettingAsync<TSetting>(TSetting setting, CancellationToken token = default)
        where TSetting : ISetting
    {
        Check.NotNull(setting, nameof(setting));
        var type = typeof(TSetting);
        var typeName = type.GetFullNameWithModule();
        var settingDefinitionRepository = provider.GetRepository<SettingDefinition>();
        var spec = new SettingDefinitionForTypeNameSpec(typeName);
        var settingDefinition = await settingDefinitionRepository.GetAsync(spec, token);
        if (settingDefinition == null)
        {
            settingDefinition = new SettingDefinition()
            {
                Name = type.Name,
                TypeName = typeName,
                Remark = type.GetDescription(),
            };
            settingDefinition.SetId();
            await settingDefinitionRepository.InsertAsync(settingDefinition, token);
        }
        var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(p => p.PropertyType.IsBaseOn<ISettingItem>()).ToArray();
        var settingItems = new List<ISettingItem>();
        foreach (var property in properties)
        {
            var value = property.GetValue(setting);
            if (value is SettingItem item)
            {
                item.Display = property.GetAttribute<DisplayNameAttribute>()?.DisplayName ?? property.Name;
                item.Remark = property.GetDescription();
                item.SettingDefinitionId = settingDefinition.Id;
                settingItems.Add(item);
            }
        }

        return await CreateOrUpdateAsync(settingItems.ToArray(), token);
    }

    /// <summary>
    /// 找出指定类型的设置定义
    /// </summary>
    /// <param name="typeName">类型名称，可是一部分关键字</param>
    /// <param name="includeItems">包含设置项</param>
    /// <param name="token"></param>
    /// <returns></returns>
    public async Task<SettingDefinition?> GetSettingDefinition(string typeName, bool includeItems = false, CancellationToken token = default)
    {
        var repository = provider.GetRepository<SettingDefinition>();
        var spec = new SettingDefinitionForTypeNameSpec(typeName);
        var settingDefinition = await repository.GetAsync(spec, token);
        if (settingDefinition != null && includeItems)
        {
            var settingItemRepository = provider.GetRepository<SettingItem>();
            var spec2 = new SettingItemByDefinitionSpec(settingDefinition.Id);
            var items = await settingItemRepository.GetAllAsync(spec2, token);
            settingDefinition.Items = items;
        }
        return settingDefinition;
    }

    private async Task<string[]> GetKeysAsync(string rootKey, CancellationToken token)
    {
        var spec = new SettingItemForRootKeySpec(rootKey);
        var keys = await _repository.GetAllAsync(spec, m => m.Key, token);
        return keys.OrderBy(m => m.Length).Distinct().ToArray();
    }

    private static string GetCacheKey(string key) => $"Settings:{key}";

}
